package com.ansion.commonchat.startup.workmanager.bwork.impl;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ansion.commonchat.startup.workmanager.bwork.BWorkerFactory;
import com.ansion.commonchat.startup.workmanager.bwork.impl.mode.WorkDataCache;
import com.ansion.commonchat.startup.workmanager.bwork.utils.BWorkerWrapper;
import com.ansion.commonchat.startup.workmanager.bwork.utils.taskexecutor.BTaskExecutor;
import com.ansion.log.LogTag;
import com.ansion.log.VLog;
import com.google.common.util.concurrent.ListenableFuture;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;


/**
 * Created by wanghongjie on 2023/5/5
 * Describe : 抽象基类，用于处理任务的调度和执行。
 * <p>
 * 作用如下:
 * 1.调度任务：Processor 根据调度策略和约束条件，决定哪些任务可以被执行。它会查询数据库，获取待执行的任务，并根据调度算法选择合适的任务进行执行。
 * 2.执行任务：Processor 负责将选中的任务分配给适当的执行器（Executor）进行执行。它会创建 Worker 实例，并将任务委派给该实例进行处理。
 * 3.处理任务状态和结果：Processor 跟踪任务的状态和结果。它会监视任务的执行情况，并根据任务的完成状态更新数据库中的相应信息。
 * 4.处理任务调度的冲突和优先级：Processor 负责处理任务调度的冲突和优先级。它会根据任务的约束条件和调度策略，决定任务的执行顺序和优先级。
 * <p>
 * 总之，Processor 是 WorkManager 内部用于调度和执行任务的核心组件。
 * 它负责任务的选择、分配和执行，并确保任务按照预期的方式进行调度和处理。
 * 通过使用 Processor，WorkManager 实现了可靠且灵活的任务调度和执行机制。
 */
public class BProcessor implements BExecutionListener {

    private Context mAppContext;
    private Executor mExecutor;
    private BTaskExecutor mWorkTaskExecutor;
    private BWorkerFactory mWorkerFactory;
    private WorkDataCache mWorkDataCache;
    private Map<String, BWorkerWrapper> mEnqueuedWorkMap;
    private List<BScheduler> mSchedulers;

    private final Object mLock;

    public BProcessor(
            @NonNull Context appContext,
            @NonNull Executor executor,
            @NonNull BTaskExecutor workTaskExecutor,
            @NonNull BWorkerFactory workerFactory,
            @NonNull WorkDataCache workDataCache,
            @NonNull List<BScheduler> schedulers) {
        mAppContext = appContext;
        mExecutor = executor;
        mWorkTaskExecutor = workTaskExecutor;
        mWorkerFactory = workerFactory;
        mWorkDataCache = workDataCache;
        mEnqueuedWorkMap = new HashMap<>();
        mSchedulers = schedulers;
        mLock = new Object();
    }

    public boolean startWork(
            @NonNull String id) {

        BWorkerWrapper workWrapper;
        synchronized (mLock) {
            if (isEnqueued(id)) {
                if (VLog.OPEN_LOG)
                    VLog.d(LogTag.TAG_B_WORK_MANAGER, "Work %s is already enqueued for processing", id);
                return false;
            }

            workWrapper =
                    new BWorkerWrapper.Builder(
                            mAppContext,
                            mExecutor,
                            mWorkTaskExecutor,
                            mWorkerFactory,
                            mWorkDataCache,
                            id)
                            .withSchedulers(mSchedulers)
                            .build();
            /**
             * 此处 future 的意义：
             * BWorkerWrapper调度任务完成后，状态变更通知 FutureListener
             * mExecutionListener.onExecuted(mWorkSpecId, needsReschedule);
             *
             * 在 onExecuted 中 ，mEnqueuedWorkMap.remove(workSpecId);
             * 再次调用 startWork 时， isEnqueued(id) 为 false ，可以继续进行调度。
             *
             * 以此来解决重复调度的问题，同一个 WorkSpecId 只调度一次。
             * mEnqueuedWorkMap.put(id, workWrapper);
             */
            ListenableFuture<Boolean> future = workWrapper.getFuture();
            future.addListener(
                    new FutureListener(this, id, future),
                    mWorkTaskExecutor.getMainThreadExecutor());
            mEnqueuedWorkMap.put(id, workWrapper);
        }
        // 转换成 BWorkerWrapper，然后再次存入队列，并按照顺序调度
        mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
        if (VLog.OPEN_LOG)
            VLog.d(LogTag.TAG_B_WORK_MANAGER, "%s: processing %s", getClass().getSimpleName(), id);
        return true;
    }

    public boolean stopWork(@NonNull String id) {
        synchronized (mLock) {
            if (VLog.OPEN_LOG)
                VLog.d(LogTag.TAG_B_WORK_MANAGER, "BProcessor stopping background work %s", id);
            BWorkerWrapper wrapper = mEnqueuedWorkMap.remove(id);
            return interrupt(id, wrapper);
        }
    }

    public boolean isEnqueued(@NonNull String workSpecId) {
        synchronized (mLock) {
            return mEnqueuedWorkMap.containsKey(workSpecId);
        }
    }

    @Override
    public void onExecuted(
            @NonNull final String workSpecId,
            boolean needsReschedule) {

        synchronized (mLock) {
            mEnqueuedWorkMap.remove(workSpecId);

            if (VLog.OPEN_LOG)
                VLog.d(LogTag.TAG_B_WORK_MANAGER, "%s %s executed; reschedule = %s",
                        getClass().getSimpleName(), workSpecId, needsReschedule);
        }
    }

    private static boolean interrupt(@NonNull String id, @Nullable BWorkerWrapper wrapper) {
        if (wrapper != null) {
            wrapper.interrupt();
            if (VLog.OPEN_LOG)
                VLog.d(LogTag.TAG_B_WORK_MANAGER, "BWorkerWrapper interrupted for %s", id);
            return true;
        } else {
            if (VLog.OPEN_LOG)
                VLog.d(LogTag.TAG_B_WORK_MANAGER, "BWorkerWrapper could not be found for %s", id);
            return false;
        }
    }

    private static class FutureListener implements Runnable {

        private @NonNull
        BExecutionListener mExecutionListener;
        private @NonNull
        String mWorkSpecId;
        private @NonNull
        ListenableFuture<Boolean> mFuture;

        FutureListener(
                @NonNull BExecutionListener executionListener,
                @NonNull String workSpecId,
                @NonNull ListenableFuture<Boolean> future) {
            mExecutionListener = executionListener;
            mWorkSpecId = workSpecId;
            mFuture = future;
        }

        @Override
        public void run() {
            boolean needsReschedule;
            try {
                needsReschedule = mFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                needsReschedule = true;
            }
            mExecutionListener.onExecuted(mWorkSpecId, needsReschedule);
        }
    }
}